package com.xunjing.order.config.sharding;

import com.google.common.collect.Range;
import com.xunjing.common.core.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Author ZYT
 * @Date 2024-03-29 18:14
 * @Version 1.0
 * @Describe 范围分片（用作数据查询）
 */
@Slf4j
@Component
public class DateRangeShardingAlgorithm implements RangeShardingAlgorithm<Timestamp> {
    /**
     * 表分片符号，例：t_contract_202201 中，分片符号为 "_"
     */
    private final String TABLE_SPLIT_SYMBOL = "_";
    /**
     * 完整时间格式
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");

    private static ShardingAlgorithmReload shardingAlgorithmReload;
    @Autowired
    public void setRangeShardingAlgorithmReload(ShardingAlgorithmReload shardingAlgorithmReload) {
        DateRangeShardingAlgorithm.shardingAlgorithmReload = shardingAlgorithmReload;
    }

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Timestamp> rangeShardingValue) {
        String logicTableName = rangeShardingValue.getLogicTableName();
        // 打印分片信息
        log.info(">>>>>>>>>> 【INFO】范围分片，节点配置表名：{}", availableTargetNames);
        Range<Timestamp> valueRange = rangeShardingValue.getValueRange();
        boolean hasLowerBound = valueRange.hasLowerBound();
        boolean hasUpperBound = valueRange.hasUpperBound();

        // 获取最大值和最小值
        LocalDateTime lowerEnd = hasLowerBound ? valueRange.lowerEndpoint().toLocalDateTime() : getLowerEndpoint(availableTargetNames);
        LocalDateTime upperEnd = hasUpperBound ? valueRange.upperEndpoint().toLocalDateTime() : getUpperEndpoint(availableTargetNames);

        // 判断这两个日期所跨越表的范围
        Set<String> resultTableNames = DateUtils.getMonthBetween(lowerEnd, upperEnd,logicTableName);
        log.info("范围分跨越分表：{}", resultTableNames);
        return getShardingTablesAndCreate(logicTableName, resultTableNames, availableTargetNames);
    }

    /**
     * 检查分表获取的表名是否存在，不存在则自动建表
     *
     * @param logicTableName       逻辑表
     * @param resultTableNames     真实表名，例：t_user_202201
     * @param availableTargetNames 可用的数据库表名
     * @return 存在于数据库中的真实表名集合
     */
    public Set<String> getShardingTablesAndCreate(String logicTableName, Collection<String> resultTableNames, Collection<String> availableTargetNames) {
        return resultTableNames.stream().map(o -> getShardingTableAndCreate(logicTableName, o, availableTargetNames)).collect(Collectors.toSet());
    }

    /**
     * 检查分表获取的表名是否存在，不存在则自动建表
     *
     * @param logicTableName  逻辑表
     * @param resultTableName 真实表名，例：t_user_202201
     * @return 确认存在于数据库中的真实表名
     */
    private String getShardingTableAndCreate(String logicTableName, String resultTableName, Collection<String> availableTargetNames) {
        // 缓存中有此表则返回，没有则判断创建
        if (availableTargetNames.contains(resultTableName)) {
            return resultTableName;
        } else {
            // 检查分表获取的表名不存在，需要自动建表
            boolean isSuccess = shardingAlgorithmReload.createShardingTable(logicTableName, resultTableName);
            if (isSuccess) {
                //添加缓存
                availableTargetNames.add(resultTableName);
                //重载缓存
                //shardingAlgorithmReload.tableNameCacheReloadAll();
                return resultTableName;
            } else {
                // 如果建表失败，返回逻辑空表(无法插入数据)
                return logicTableName;
            }
        }
    }

    /**
     * 获取 最小分片值
     *
     * @param tableNames 表名集合
     * @return 最小分片值
     */
    private LocalDateTime getLowerEndpoint(Collection<String> tableNames) {
        Optional<LocalDateTime> optional = tableNames.stream()
                .map(o -> LocalDateTime.parse(o.replace(TABLE_SPLIT_SYMBOL, "") + "01 00:00:00", DATE_TIME_FORMATTER))
                .min(Comparator.comparing(Function.identity()));
        if (optional.isPresent()) {
            return optional.get();
        } else {
            log.error(">>>>>>>>>> 【ERROR】获取数据最小分表失败，请稍后重试，tableName：{}", tableNames);
            throw new IllegalArgumentException("获取数据最小分表失败，请稍后重试");
        }
    }

    /**
     * 获取 最大分片值
     *
     * @param tableNames 表名集合
     * @return 最大分片值
     */
    private LocalDateTime getUpperEndpoint(Collection<String> tableNames) {
        Optional<LocalDateTime> optional = tableNames.stream()
                .map(o -> LocalDateTime.parse(o.replace(TABLE_SPLIT_SYMBOL, "") + "01 00:00:00", DATE_TIME_FORMATTER))
                .max(Comparator.comparing(Function.identity()));
        if (optional.isPresent()) {
            return optional.get();
        } else {
            log.error(">>>>>>>>>> 【ERROR】获取数据最大分表失败，请稍后重试，tableName：{}", tableNames);
            throw new IllegalArgumentException("获取数据最大分表失败，请稍后重试");
        }
    }


}

